<?php
ArrowController::importModel("org.arrowplatform.orm.db.ArrowOrmDb");
/**
 * @author Pawel Giemza
 * @version 1.0
 * @package Arrow
 * @subpackage Orm
 * @link http://arrowplatform.org/
 * @copyright 2009 3code 
 * @license GNU LGPL
 * 
 * @date 2009-03-06 
*/

/**
 * Generates MySQL satement using ORM objects
 *
 * This class connects ORM to Mysql databases and generates sql statement from ORM objects (criteria, selector).
 */
class ArrowOrmMysql implements ArrowOrmDb{

	/**
	 * Connection to DB
	 *
	 * @var Resource
	 */
	protected $dbConnection;

	/**
	 * Transaction status (enabled/disabled)
	 *
	 * @var bool
	 */
	public $transaction;
	
	/**
	 * Name with which this DB is reffered by other ORM objects
	 *
	 * @var String
	 */
	protected $alias;
	
	/**
	 * Contains last db query
	 * 
	 * @var String
	 */
	private static $lastQuery = "";
	
	/**
	 * 
	 * If true query is execute 
	 * @var bool 
	 */
	private static $execute = true;
	
	/**
	 * Query to inject insted current query
	 * @var string - query to inject
	 */
	private static $query_injection = "" ;
	
	/**
	 * 
	 * if true time of execute queries is counted
	 * @var bool
	 */
	private static $query_time_count = false ;
	
	/**
	 * 
	 * time in seconds [float] of execute query if $query_time_count is true
	 * @var float
	 */
	private static $query_time = 0 ;
	
	//List of columns containing aggregates 
	//Key - name of column from which aggregate was made
	//Value -  name of created column
	private $aggregated_columns = array();
	
	/**
	 * Constuctor. Opens connection to DB
	 *
	 * @param String $host
	 * @param String $user
	 * @param String $password
	 * @param String $dbname
	 * @param bool $transaction
	 * @param String $alias
	 */
	public function __construct($host, $user, $password, $dbname, $charset, $transaction, $alias)
	{	
		$this->alias = $alias;
		//$this->transaction = false;
		if (!$this->dbConnection = mysql_connect($host, $user, $password, true) )
			$this->error("mysql_connect($host, $user, $password, true)",mysql_error());

		if (!mysql_select_db($dbname, $this->dbConnection))
			$this->error("mysql_select_db($dbname, {$this->dbConnection})",mysql_error($this->dbConnection));
		
    //ArrowLogger::log("[Mysql] Object:{$this->alias}; Connection established to \"{$dbname}\" at \"{$host}\" )" , ArrowLogger::EL_INFO );	
			
		mysql_query("SET NAMES {$charset}", $this->dbConnection);
		mysql_query("SET CHARACTER SET {$charset}", $this->dbConnection);

    //ArrowLogger::log("[Mysql] Object:{$this->alias}; Names set to: {$charset}; Charset set to:{$charset}  )" , ArrowLogger::EL_INFO );	

		if ($transaction)
		{
			if(!mysql_query("START TRANSACTION", $this->dbConnection))
				$this->error("mysql_query('START TRANSACTION', $this->dbConnection)",mysql_error($this->dbConnection));
			$this->transaction = true;
			//ArrowLogger::log("[Mysql] Object:{$this->alias}; Transaction started )" , ArrowLogger::EL_INFO );	
		}				
	  register_shutdown_function(array($this, 'destruct'));	
	}
	
	/**
	 * Destructor. Closes connection to DB.
	 *
	 */
	public function destruct(){
		//ArrowLogger::log("Entering mysql::destruct",ArrowLogger::EL_INFO);		
		if ($this->transaction){
			
			if (connection_status() !=0){
				if(!mysql_query( "ROLLBACK", $this->dbConnection))							
					$this->error("mysql_query('ROLLBACK', {$this->alias})",mysql_error($this->dbConnection));
				//ArrowLogger::log("[Mysql] Object:{$this->alias}; Transaction rolled back",ArrowLogger::EL_INFO);		
			}
			else{
				if( ArrowController::$script_end_ok ) {
					if (!mysql_query("COMMIT", $this->dbConnection))
						$this->error("mysql_query('COMMIT', {$this->alias})",mysql_error($this->dbConnection));
				}
				//ArrowLogger::log("Object:$this->alias; Transaction commited",ArrowLogger::EL_INFO);
			}
		}
		
		if(!mysql_close($this->dbConnection))
			$this->error("mysql_close({$this->alias})",mysql_error($this->dbConnection));
		//ArrowLogger::log("DB:{$this->alias}; Connection closed",ArrowLogger::EL_INFO);
	}

	/**
	 * Error handler
	 *
	 * @param String $query
	 * @param String $error
	 */
	private function error( $action, $error )
	{
		if ($this->transaction)
		{
			if(!mysql_query( "ROLLBACK", $this->dbConnection)){
				//ArrowLogger::log("[Mysql] Object:{$this->alias}; Can not rollback transaction;".mysql_error(),ArrowLogger::EL_ERROR);		
			}else{	
				//ArrowLogger::log("[Mysql] Object:{$this->alias}; Transaction rolled back",ArrowLogger::EL_INFO);
			}			
		}
		//ArrowLogger::log("[Mysql] Object:{$this->alias}; Error \"{$error}\" encountered while executing \"{$action}\" ",ArrowLogger::EL_ERROR);		
		//$action = nl2br($action);
		throw new ArrowOrmException( array( "msg" => "[Mysql] Object:{$this->alias}; Error \"{$error}\" encountered while executing query", "query" => "{$action}"));//TODO::Tekst wyjątku	
	}

	/**
	 * Returns rows
	 *
	 * @param String $table
	 * @param Criteria $criteria
	 * @return Array
	 */
	public function select($table, $criteria, $getQuery = false)
	{		
		$query = "SELECT ". $this->columnsToSQL($criteria) . " FROM $table WHERE ".$this->conditionsToSQL($criteria). $this->groupsToSQL($criteria);
		if($getQuery)
			return $query;
		return $this->toArray($this->query($query));				
	}	
	
	/**
	 * Inserts new row into database
	 *
	 * @param String $table
	 * @param Array $arrData
	 * @return int (id of object inserted into table)
	 */
	public function insert($table, $data)
	{		
		$query = "";
		if (empty($data))
			$query = "INSERT INTO $table () VALUES ()";
		else
		{
			$query = "INSERT INTO $table (`" . implode( "`, `", array_keys( $data ) )."`) ";
			$query.= "\n VALUES ( ";
			
			$first = true;
			foreach ($data as $str)
			{
				if (!$first)
					$query.=", ";	
	
				if( is_null($str) )
					$query.= "NULL";
				else
					$query.=( is_int($str) || is_float($str) )?$str:"'".mysql_real_escape_string($str, $this->dbConnection)."'";			

				$first = false;
			}
			$query.=  " )";
		}

    $this->query($query);
    $objID =  mysql_insert_id($this->dbConnection);
    
    return $objID;
	}

	/**
	 * Updates db
	 *
	 * @param String $table
	 * @param Criteria $id
	 * @param values $arrReplace
	 * @return unknown
	 */
	public function update($table, $data, $criteria)
	{
		if(empty($data))
			return false;
		
		$query = "UPDATE $table SET ";
		$first = true;

		if (is_array($data)) 
		{
			foreach ($data as $column => $value) 
			{
				$query .= (($first) ? '': ', ') . "`$column`" . '=';

				if( is_null($value) )
					$query.= "NULL";
				else
					$query.=( is_int($value) || is_float($value) )?$value:"'".mysql_real_escape_string($value, $this->dbConnection)."'";			
									
				$first = false;
			}
		}
		else
			$this->error( 'mysql::update', 'Argument $data must be an array.' );

		$query.= " WHERE ".$this->conditionsToSQL( $criteria, $this->dbConnection);
		$ret = $this->query($query);
		return $ret;
	}

	/**
	 * Deletes rows specified by Criteria 
	 *
	 * @param String $table
	 * @param Criteria $criteria
	 */
	public function delete($table, $criteria)
	{
		$query = "DELETE FROM $table WHERE ".$this->conditionsToSQL($criteria);
		if (isset($options['returnSql']) && $options['returnSql'] == 1 )
			return $query;
		else			
			$this->query($query);			
	}

	/**
	 * Query handle
	 *
	 * @param String $query
	 * @return DbResultHandle
	 */
	public function query( $query, $multiInsert=false )
	{
    //ArrowLogger::log("[ArrowOrmMysql] Object:{$this->alias}; query( $query, $multiInsert )" , ArrowLogger::EL_INFO );
		if( !empty( self::$query_injection ) ) {
			$query = self::$query_injection ;
			self::$query_injection = "" ;
		} 
    	self::$lastQuery = $query;
    	$result = null;
    	$db_sq_time = 0 ;
    	$db_eq_time = 0 ;
	    if( self::$execute ) {
	    	if( self::$query_time_count ) {
	    		$db_sq_time = microtime( true ) ;
	    	}
	    	
		    	if ($multiInsert){
					$queries = explode(';',$query);
					foreach ($queries as $q){
						$q = trim($q);
						if (!empty($q)){
							if (!$result = mysql_query( $q, $this->dbConnection ))
								$this->error( $q, mysql_error($this->dbConnection) );
						}
					}		
				}
				else
				if (!$result = mysql_query( $query, $this->dbConnection ))
					$this->error( $query, mysql_error($this->dbConnection) );
	    	if( self::$query_time_count ) {
	    		$db_eq_time = microtime( true ) ;
	    		self::$query_time += $db_eq_time - $db_sq_time ;
	    	}
					
	    }
	    
	   // if( isset( $_SESSION["SQL_DEBUG"] ) )  
	    FB::log($query, $db_eq_time - $db_sq_time );
	    
		return $result;
	}
	
	/**
	 * Returns last db query
	 * 
	 * @return String
	 */
	public static function getLastQuery(){
		return self::$lastQuery;
	}
	
	public static function turnOffExecute()  {
		self::$execute = false ;
	}
	
	public static function turnOnExecute()  {
		self::$execute = true ;
	}

	public static function injectQuery( $query ) {
		throw new ArrowException( "[ArrowOrmMysql::injectionQuery] deprecated" ) ;
		self::$query_injection = $query ;
	}
	/**
	 * Changing db result to array
	 *
	 * @param DbResultHandle $result
	 * @return Array
	 */
	public function toArray( $result )
	{
		if( $result == null ) return array() ;
		$tmp = array();
		while( $line = mysql_fetch_assoc($result) ){
			$tmp[] = $line;
		}		
		return $tmp;
	}

	public function join($criterias, $getQuery = false)
	{
		
		$links = array();
		$keys = array();
		$tables = array();
		$conditions = array();
		$joined_classes = array(); //classes which already have been joined
		$other_classes = array(); //classes not joined yet
		//$add_info = array();
		$aliases = array();

		$joinType = $criterias->getJoinType();
		
		foreach ($criterias as $alias=>$criteria){
			//$conditions[] = $this->conditionsToSQL($criteria);
    		$class = explode('.',$criteria->getModel());
    		$class = end($class);
    		if (strpos($alias,'[') === false){
    			//only add base criterias
			    $conf = ArrowOrmConfig::getClassConf($class);
	    		if (empty($joined_classes))
					$joined_classes[] = $class;
				else
					$other_classes[] = $class;	
				if (isset($conf['key']['foreign']))
					$links[$class] = $conf['key']['foreign'];
				$keys[$class] = $conf['key']['primary'];
				$tables[$class]= $conf['attr']['table'];
    		}
		}
		$allowedRelations= $criterias->getAvailableRelations();
		
		
		foreach ($links as $baseClass=>$relClasses)
			foreach ($relClasses as $relClass=>$relData)
				if (!isset($allowedRelations[$baseClass][$relClass]))
					unset($links[$baseClass][$relClass]);
			
		$virtual_links = $criterias->getVirtualRelations() ;
		
		$join = $tables[$joined_classes[0]];
		$new_join = array(); //joins between this table and currently joined tables  
		//$table_aliases = array();
		foreach ($other_classes as $oid=>$new_class){
			$new_join = array();
			foreach ($joined_classes as $class){
				//check if there is link: $class_new => class
				if (isset($links[$new_class][$class])){
					$add = $links[$new_class][$class];
					if (is_array($add)){
						foreach ($add as $field){
							$new_join[]= array('base_class'=>$class, 'base_field'=>$keys[$class], 'join_class'=>$new_class,'join_field'=>$field);
						}
					}
					else{
						$new_join[]= array('base_class'=>$class, 'base_field'=>$keys[$class], 'join_class'=>$new_class,'join_field'=>$add);
					}
				}
				//check if there is link: $class => class_new (for n:m join table)
				if (isset($links[$class][$new_class])){
					//it is possible to join classes
					$add = $links[$class][$new_class];
					if (is_array($add)){
						foreach ($add as $field){
							$new_join[]= array('base_class'=>$class, 'base_field'=>$field, 'join_class'=>$new_class,'join_field'=>$keys[$new_class]);
						}
					}
					else{
						$new_join[]= array('base_class'=>$class, 'base_field'=>$add, 'join_class'=>$new_class,'join_field'=>$keys[$new_class]);
					}
				}
				
				// check in virtual connecctions
				
				if (isset($virtual_links[$class][$new_class])){ //it is possible to virtual join classes
					$add = $virtual_links[$class][$new_class];
					foreach ($add as $field){
						$new_join[]= array('base_class'=>$class, 'base_field'=>$field["base_field"], 'join_class'=>$new_class,'join_field'=>$field["join_field"]);
					}
				}
				
				if (isset($virtual_links[$new_class][$class])){ //it is possible to virtual join classes
					$add = $virtual_links[$new_class][$class];
					foreach ($add as $field){
						$new_join[]= array('base_class'=>$class, 'base_field'=>$field["base_field"], 'join_class'=>$new_class,'join_field'=>$field["join_field"]);
					}
				}
				
			}
			unset($other_classes[$oid]);
			$joined_classes[] = $new_class;
			if (!empty($new_join)){
				foreach ($new_join as $join_row){
					//$alias = '';
					$table = $tables[$join_row['join_class']];
					//$alias = 'AS `'.$join_row['join_class'].'_'.$join_row['base_class'].'_'.$join_row['base_field'].'`';
					$alias = "`{$join_row['join_class']}_{$join_row['base_class']}_{$join_row['base_field']}`" ;
					$alias_columns = $join_row['join_class'] ;
					//$add_info[] = 
					if (count($new_join) > 1){
						$alias_columns = "{$join_row['join_class']}[{$join_row['base_class']}::{$join_row['base_field']}]" ;
					}
					
					//if (count($new_join) > 1){
						//$alias = 'AS `'.$join_row['join_class'].'_'.$join_row['base_class'].'_'.$join_row['base_field'].'`';
						//$table =$join_row['join_class'].'_'.$join_row['base_class'].'_'.$join_row['base_field'];
						//$add_info[] = array('class'=>$join_row['join_class'], 'alias'=>$join_row['join_class'].'['.$join_row['base_class'].'::'.$join_row['base_field'].']');
					//}
					$on_conditions = "" ;
					$join_on_criteria = $criterias->getJoinOnConditions( $join_row['base_class'] ,$join_row['join_class'] );
					foreach( $join_on_criteria as $condit ) {
						//$on_conditions .= " AND {$table}.$condit " ; 
						$on_conditions .= " AND {$alias}.$condit " ;
					}
					$aliases[$tables[$join_row['join_class']]][$join_row['base_class'].'_'.$join_row['base_field']] = $alias ;
					$aliases[$tables[$join_row['join_class']]]["alias"] = $alias ; 
					$aliases[$tables[$join_row['join_class']]]["columns"][] = array('class'=>$join_row['join_class'], 'alias_column'=> $alias_columns, 'alias' => $alias );
					$base_alias = $tables[$join_row['base_class']] ;
					if( isset( $aliases[$tables[$join_row['base_class']]] ) ) {
						$base_alias = $aliases[$tables[$join_row['base_class']]]["alias"];
					}
					//$table_aliases[$tables[$join_row['join_class']]][$join_row['base_class'].'_'.$join_row['base_field']] = $alias ;// $table; 
					$join .= "\r\n\t {$joinType} JOIN {$tables[$join_row['join_class']]} AS {$alias} ON ({$base_alias}.{$join_row['base_field']}=$alias.{$join_row['join_field']} {$on_conditions} )";
				}
			}
			else
				throw new ArrowOrmException( array( "msg" => "[ArrowOrmMysql] Could not join tables delivered to join function, check whether all relations have been defined", "tables" => $tables, 'relations'=>$links, 'virtual'=>$virtual_links,'already_joined'=>$joined_classes, 'to_join'=>$other_classes) ,1);
		}
		
		foreach( $criterias as $criteria ) $conditions[] = $this->conditionsToSQL($criteria, $aliases);
		
		$conditions = implode(' AND ', $conditions);
		
		$columnConditions = $this->searchConditionsToSQL($criterias->getSearchConditions(), $criterias->getBaseClass(), $aliases );
		
		$columns = $this->columnsToSQL($criterias, /*$add_info*/ $aliases);
		
		$groups = $this->groupsToSQL($criterias,/*$table_aliases*/ $aliases);
		$query = 'SELECT '. $columns.' FROM '.$join.' WHERE '. $conditions.' AND '.$columnConditions.' '.$groups;
		
		/*static $i = 1 ;
		if( $i == 7) {
		echo "<pre>" ;
			//print_r( $add_info );
		//print_r($aliases);
		//print_r("columns: ".$columns."<br />");
		//print_r("join: ".$join."<br />");
		print_r("where: ".$conditions."<br />");
		//print_r("columnConditions: ".$columnConditions."<br />" );
		//print_r( "groups: <h2>".$groups."</h2><br />" );
		//list tables to join
		
		print_r($query);
		exit;
		}
		$i++;
		*/
		
		
		if($getQuery)
			return $query;
		
		$ret = $this->query($query);
		$data = $this->toArray($ret);
		return $data;
	}	
	
	/**
	 * Creates sql condition
	 *
	 * @param Criteria $criteria
	 * @return String
	 */	
	public function conditionsToSQL($criteria, $aliases=array())
	{		
    	$crit_data = $criteria->getData();
    	$class = explode('.',$crit_data['model']);
    	$class = end($class);    
    	$conf = ArrowOrmConfig::getClassConf($class);
    	$dbConnection = $this->dbConnection;
		$conditionString ="";
		$quote = "";
		$table = '';
		$table = $conf['attr']['table'];
		if (isset($crit_data['conditions']) )
      	foreach($crit_data['conditions'] as $cond ){		
			//$column = "`".$cond['column']."`";
			$column = $cond['column'];
			$condition = $cond['condition'];
			$value = $cond['value'];
			 
			//Column aliasing
			if ( $table!='' && $condition!=ArrowOrmCriteria::C_AND_GROUP && $condition!=ArrowOrmCriteria::C_OR_GROUP && $condition!=ArrowOrmCriteria::START && $condition!=ArrowOrmCriteria::END ) {
				if( isset( $aliases[$table]) ) {
          			$column = $aliases[$table]["alias"].'.'.$column;
          		} else $column = "{$table}.{$column}";
			} 
			
          	//$column = $this->traslateColumn($column,$class,$aliases);
          	
			//Value quoting
			$val = "" ;
			if( is_array($value) && !empty( $value ) ) $val = reset( $value );
			if ( ( is_string($value) && $value!=='NULL' && $condition != ArrowOrmCriteria::C_RELATION && $condition != 'CUSTOM') ||(!empty($val) && is_string($val) ) )
			{
        		$quote = "'";
				if (!is_array($value))
					$value =  $quote.mysql_real_escape_string($value, $dbConnection).$quote;
				else
				foreach($value as $key => $obj){
					if ($obj !== 'NULL')
						$value[$key] =  $quote.mysql_real_escape_string($obj, $dbConnection).$quote;
					if (is_null($value) && !in_array($condition,array('AND','OR','END') ) )
						$value = 'NULL';
					if ($value[$key] === '') $value[$key]="''";
				}		
			}
			if (is_null($value) && !in_array($condition,array('AND','OR','END') ) )
				$value = 'NULL';
			if ($value === '') $value="''";
			
		//Condition Generation
		$conditionString.="\r\n\t";
		if ($value === 'NULL'){
        	if ($condition == ArrowOrmCriteria::C_EQUAL)
          		$conditionString.= $column. " IS NULL ";
        	else
          		$conditionString.= $column. " IS NOT NULL ";				
		}
		else switch ($condition){
        case 'CUSTOM':
          // aliasowanie custom condition
          if (isset($cond['tables']))
          foreach( $cond['tables'] as $at ) {
          	if( isset( $aliases[$at] ) )
          		$value = str_replace( $at, $aliases[$at]["alias"], $value );
          }	
          $conditionString.= $value;
        break;
        case ArrowOrmCriteria::C_EQUAL:
          $conditionString.= $column." = ".$value;
        break;
        case ArrowOrmCriteria::C_NOT_EQUAL:
          $conditionString.= $column." != ".$value;
        break; 
        case ArrowOrmCriteria::C_GREATER_EQUAL:
          $conditionString.= $column." >= ".$value;
        break; 
        case ArrowOrmCriteria::C_GREATER_THAN:
           $conditionString.= $column." > ".$value;       
        break; 
        case ArrowOrmCriteria::C_LESS_EQUAL:
            $conditionString.= $column." <= ".$value;      
        break; 
        case ArrowOrmCriteria::C_LESS_THAN: 
          $conditionString.= $column." < ".$value;
        break; 
        case ArrowOrmCriteria::C_IN:
        	//add null handling
        	$valuesIn = array();
        	$null = false;
        	foreach ($value as $add_val){
        		if ($add_val === 'NULL')
	        		$null = true;
	        	else if( trim($add_val) !== '' )
		        	$valuesIn[] = $add_val;	
        	}
        	if (!empty ($valuesIn))
	        	$add_condition = $column." IN (".implode(", ",$valuesIn).")";
	        else
	        	$add_condition = ' 0 ';
			if ($null)
				$add_condition = "(".$add_condition."OR {$column} IS NULL " .")";
        	$conditionString.= $add_condition;
        break; 
        case ArrowOrmCriteria::C_NOT_IN:
        	$valuesIn = array();
        	$null = false;
        	foreach ($value as $add_val){	
	        	if ($add_val === 'NULL')
	        		$null = true;
	        	else if( trim($add_val) !== '' )
		        	$valuesIn[] = $add_val;	
        	}
        	if (!empty($valuesIn))
	        	$add_condition = $column." NOT IN (".implode(", ",$valuesIn).")";
	        else
	         $add_condition = ' 1 ';
			if ($null)
				$add_condition = "(".$add_condition."AND {$column} IS NOT NULL " .")";
        	$conditionString.= $add_condition;
        break; 
        case ArrowOrmCriteria::C_BETWEEN:
			$conditionString.= $column." BETWEEN {$value[0]} AND {$value[1]} ";    
        break;
        case ArrowOrmCriteria::C_RELATION:   
          $conditionString.= $column." = ".$value;  
        break;
        case ArrowOrmCriteria::C_LIKE:
        case ArrowOrmCriteria::C_ILIKE: 
          $conditionString.= $column." LIKE ".$value;        
        break; 
        case ArrowOrmCriteria::C_NOT_LIKE:
        case ArrowOrmCriteria::C_NOT_ILIKE:
          $conditionString.= $column." NOT LIKE ".$value;        
        break;
		case ArrowOrmCriteria::C_BIT_OR:
          $conditionString.= $column." | ".$value;
        break; 
        case ArrowOrmCriteria::C_BIT_AND:
          $conditionString.= $column." & ".$value;
        break;
		case ArrowOrmCriteria::C_BIT_XOR:
			$conditionString.= $column." ^ ".$value;
		break;
        case ArrowOrmCriteria::C_OR_GROUP:
          $conditionString.= " OR ";           
        break; 
        case ArrowOrmCriteria::C_AND_GROUP:
          $conditionString.= " AND ";  
        break; 
        case ArrowOrmCriteria::START:
          $conditionString.= "( ";          
        break; 
        case ArrowOrmCriteria::END:
          $conditionString.= " )";  			
        break;
			}				
		}
		if( empty($conditionString) )	
			$conditionString = '1';		
		return $conditionString;
	}
	//-------------------------------------------------------------------------------------------------------------
	public function traslateColumn($column,$mainClass, $aliases = array() ){
		
		$pattern = '/(.*?)(\[(.*)\])?:(.*)/';
		preg_match($pattern, $column, $matches);
		if (!empty($matches)){
			$class = $matches[1];			
			$conf = ArrowOrmConfig::getClassConf($class);
			$table = $conf['attr']['table'];
			$field = $matches[4];
			$field = str_replace(':','',$field);
		}
		else{
			$class = $mainClass;
			$conf = ArrowOrmConfig::getClassConf($class);
			$table = $conf['attr']['table'];
			$field = $column;
		}
		
		if ( !empty($matches[3]) ){
			if( strpos($matches[3],'::')===false){
				$rel = $mainClass.'_'.$matches[3];
			}	
			else
				$rel = str_replace('::','_',$matches[3]);
			return "`{$class}_{$rel}`.`{$field}`";	
		}
		
		if( isset( $aliases[$table] ) ) $table = $aliases[$table]['alias'] ;
		else $table = '`'.$table.'`' ;
			//column from base class
			return $table.".`{$field}`";
	}
	//-------------------------------------------------------------------------------------------------------------
	/**
	 * Creates sql condition
	 *
	 * @param Criteria $criteria
	 * @return String
	 */	
	public function searchConditionsToSQL($data,$baseClass,$aliases=array())
	{		
		$glue = ' AND ';
		$conditionString = '';
		$dbConnection = $this->dbConnection;
		foreach ($data as $condition){
			//add glue
			if (!empty($conditionString))
			$conditionString.= $glue;
			//-----
			$columns = $condition['columns'];
			$value = $condition['value']; 
			$cond = $condition['condition'];
			$quote = "'";
			$value =  $quote.mysql_real_escape_string($value, $dbConnection).$quote;
			$conditionString.= ' ( ';
			$first_col = true;
			foreach ($columns as $column){
				if ($first_col == false)
					$conditionString.= ' OR ';
				
				$first_col = false;	
				//decode column names
				$column = $this->traslateColumn($column,$baseClass,$aliases);
		
				/*if (is_array($value)){
					foreach($value as $rec)
						$rec = $this->traslateColumn($rec,$baseClass);
				}
				else
					$value = $this->traslateColumn($value,$baseClass);
				*/	
				switch ($cond){
		        case 'CUSTOM':
		          $conditionString.= $value;
		        break;
		        case ArrowOrmCriteria::C_EQUAL:
		          $conditionString.= $column." = ".$value;
		        break;
		        case ArrowOrmCriteria::C_NOT_EQUAL:
		          $conditionString.= $column." != ".$value;
		        break; 
		        case ArrowOrmCriteria::C_GREATER_EQUAL:
		          $conditionString.= $column." >= ".$value;
		        break; 
		        case ArrowOrmCriteria::C_GREATER_THAN:
		           $conditionString.= $column." > ".$value;       
		        break; 
		        case ArrowOrmCriteria::C_LESS_EQUAL:
		            $conditionString.= $column." <= ".$value;      
		        break; 
		        case ArrowOrmCriteria::C_LESS_THAN: 
		          $conditionString.= $column." < ".$value;
		        break; 
		        case ArrowOrmCriteria::C_IN:
		        	//add null handling
		        	$valuesIn = array();
		        	$null = false;
		        	foreach ($value as $add_val){
		        		if ($add_val === 'NULL')
			        		$null = true;
			        	else
				        	$valuesIn[] = $add_val;	
		        	}
		        	$add_condition = $column." IN (".implode(", ",$valuesIn).")";
					if ($null)
						$add_condition = "(".$add_condition."OR {$column} IS NULL " .")";
		        	$conditionString.= $add_condition;
		        break; 
		        case ArrowOrmCriteria::C_NOT_IN:
		        	$valuesIn = array();
		        	$null = false;
		        	foreach ($value as $add_val){	
			        	if ($add_val === 'NULL')
			        		$null = true;
			        	else
				        	$valuesIn[] = $add_val;	
		        	}
		        	$add_condition = $column." NOT IN (".implode(", ",$valuesIn).")";
					if ($null)
						$add_condition = "(".$add_condition."AND {$column} IS NOT NULL " .")";
		        	$conditionString.= $add_condition;
		        break; 
		        case ArrowOrmCriteria::C_BETWEEN:
								$conditionString.= $column." BETWEEN {$value[0]} AND {$value[1]} ";    
		        break;
		        case ArrowOrmCriteria::C_RELATION:   
		          $conditionString.= $column." = ".$value;  
		        break;
		        case ArrowOrmCriteria::C_LIKE:
		        case ArrowOrmCriteria::C_ILIKE: 
		          $conditionString.= $column." LIKE ".$value;        
		        break; 
		        case ArrowOrmCriteria::C_NOT_LIKE:
		        case ArrowOrmCriteria::C_NOT_ILIKE:
		          $conditionString.= $column." NOT LIKE ".$value;        
		        break;
		        case ArrowOrmCriteria::C_BIT_OR:
		          $conditionString.= $column." | ".$value;
		        break; 
		        case ArrowOrmCriteria::C_BIT_AND:
		          $conditionString.= $column." & ".$value;
		        break;
				case ArrowOrmCriteria::C_BIT_XOR:
		          $conditionString.= $column." ^ ".$value;
		        break;
		        case ArrowOrmCriteria::C_OR_GROUP:
		          $conditionString.= " OR ";           
		        break; 
		        case ArrowOrmCriteria::C_AND_GROUP:
		          $conditionString.= " AND ";  
		        break; 
		        case ArrowOrmCriteria::START:
		          $conditionString.= "( ";          
		        break; 
		        case ArrowOrmCriteria::END:
		          $conditionString.= " )";  			
		        break;
				}
			}
			$conditionString.= ' ) ';
		}			
		if( empty($conditionString) )	
			$conditionString = '1';		
		return $conditionString;
	}
//-------------------------------------------------------------------------------------------------------------	
	public function columnsToSQL($criterias,/*$add_data=array()*/ $aliases = array()){
		$this->aggregated_columns = array();
		if (!is_array($criterias) && !( $criterias instanceof ArrowOrmJoinCriteria ) )
			$criterias = array($criterias);
		
		$distinct = false;
		$columns = array(); //list of columns to be displayed
		$change_table_name = false; //change table name to delivered alias (required for multi join)

		foreach ($criterias as $criteria){
			$change_table_name = false;
			$crit_data = $criteria->getData();
		    $class = explode('.',$crit_data['model']);
		    $class = end($class);
    
		    $conf = ArrowOrmConfig::getClassConf($class);
		
			$tableName = $conf['attr']['table'];
			$prefix_data = array();  
			// list join prefixes for this class
			/*foreach ($add_data as $prefix_info){
				if ($prefix_info['class'] === $class){
					$prefix_data[]= $prefix_info['alias'].'::';
					$change_table_name = true;
				}	
			}*/
			
			if( isset( $aliases[$tableName] ) ) {
				$prefix_data = $aliases[$tableName]["columns"] ;
				$change_table_name = true;
			}
			
			if (empty($prefix_data) )
				if( count($criterias) == 1 ) $prefix_data = array( array( "alias_column" => '' ) );  //for non join situations
			else $prefix_data = array( array( "alias_column" => $class ) );		//prefixing for basic join
				
			if(isset($crit_data['columns'])){
				foreach($prefix_data as $prefix) { 
					$ag_data = ''; // name of column for aggregated column list (which is used for translation so that aggregates can be used to sort)
					$alias_column = $prefix["alias_column"] ;
					
					if( !empty( $alias_column ) ) $alias_column  .= ':' ;
					
					foreach($crit_data['columns'] as $col){
							if ($change_table_name === true){
							 //$tableName = substr($prefix,0,-2);
							 $tableName = $prefix["alias"];//str_replace(array('::','[',']'),array('_','_',''),$tableName);
							}
						$tmp = '';
						if (!is_null($col['aggregate']) && $col['aggregate'] !='') {
							
							$agdistinct = "" ;
							if ($col['aggregate'] == Criteria::A_COUNT_DISTINCT  ) {
								$agdistinct = "DISTINCT";
								$col["aggregate"] = "COUNT" ;
							}
						
							$tmp = $col['aggregate']."($agdistinct ".$tableName.'.'.$col['column'].")" ;
							$ag_data = $tableName.'.'.$col['alias']; //use alias instead of column because it will be used as column name in order by,..
							//$ag_data = str_replace('`','',$ag_data);
						} else
							$tmp = $tableName.'.`'.$col['column'].'`';
						
						$alias = '';
						/*WIELKIE TODO: nie wiem ju jak to działa
						 * Jeśli alias column jest puste to zostawiamy stary system 
						 * jeśli alias column jest ustawione to zamieniamy je na tableName
						 * 
						 */	
						//if (empty($col['aggregate'])){
							if (!is_null($col['alias']) && $col['alias'] !=''){
								$alias = $alias_column.$col['alias'];
							}else{
								$alias = $alias_column.$col['column'];
							}
						//}
						/*else{
							if (!is_null($col['alias']) && $col['alias'] !=''){
								$alias = $col['alias'];
							}else{
								$alias = $col['column'];
							}
							if ( $alias_column !=''){
								$alias = str_replace('`','',$tableName).'.'.$alias;
							}
						}*/					
						$columns[] = $tmp. " AS `". $alias ."`";
						
						
						if (!empty($ag_data))
							$this->aggregated_columns[$ag_data] = $alias;
					}
				}
			}
			
			if ($distinct === false){
				if (isset($crit_data['distinct'])) {
					$distinct = $crit_data['distinct'];	
				} else
					$distinct = '';
			}	
		}
		$columns = implode( ", \r\n\t", $columns );
		return  $distinct." \r\n\t".$columns."\r\n"; 
	}
//--------------------------------------------------------------------------------------------------------------
	public function groupsToSQL($criterias, /*$table_aliases=array()*/ $aliases = array() )
	{	
		//FB::log($this->aggregated_columns);
		// table alliases -  array of tables whose columns have to added under special aliases (ad also many times)
		$groupby = '';
		$orderby = array();//zmiana z powodu prorytetów w joincriterii
		$limitby = '';
		
		if (!is_array($criterias) && !( $criterias instanceof ArrowOrmJoinCriteria ))
			$criterias = array( 'Persistent'=> $criterias );
		
		foreach ($criterias as $className => $criteria)
		{
			
			$order_empty = false;
			$group_empty = false;
      	$crit_data = $criteria->getData();
    	$class = explode('.',$crit_data['model']);
    	$class = end($class);    
     	$conf = ArrowOrmConfig::getClassConf($class);
			$tableName = $conf['attr']['table'];
			/*if (isset($table_aliases[$tableName]) ){
				$className = explode('[',$className);
				$className = end($className);
				$className = str_replace(array('::',']'),array('_',''),$className);
				if (isset($table_aliases[$tableName][$className]) )
					$tableName = $table_aliases[$tableName][$className];
			}*/
			$direct = true; //we use direct naming (table / column) 
			if( isset( $aliases[$tableName] ) )	{
				
				if( strpos($className,':') !== false ) {
					$className = explode('[',$className);
					$className = end($className);
					$className = str_replace(array('::',']'),array('_',''),$className);
				}				
				if (isset($aliases[$tableName][$className]) ) {
					$tableName = $aliases[$tableName][$className];
				} else {
					$tableName = str_replace('`','',$aliases[$tableName]["alias"])  ;
					$direct = false; //inderect naming (generated table alias)
				}
			}
			
			$groups = '';
			if (isset($crit_data['group']) )			
			$groups = $crit_data['group'];
			if( !empty($groups) ){
				if (empty($groupby)){
				 $groupby = "\r\n GROUP BY \r\n\t";
				 $group_empty = true;
				} 
				$tmp = array();
				foreach ($groups as $group){
					 if ($direct)
						$tmp[]= $tableName.".`".$group."`";
					else
						$tmp[]= "`".$tableName.".".$group."`";
				}
        if(!$group_empty)
            $groupby.= ", ";
				$groupby.= implode(", \r\n\t", $tmp);
			}
			
			$orders = '';
			if (isset($crit_data['order']) )
        	$orders = $crit_data['order'];
              if( !empty($orders) ){ 
                   /*if (empty($orderby)) {
                   		$orderby = ' ORDER BY ';
                   		$order_empty = true; 
                   }*/
                   foreach ($orders as $order) {
                   	 $tmp = ''; 
                       if ($order[0] == "RAND()" || $order[0] == "RAND" )
                        $tmp= "RAND()";  
                       else{
                        if ($direct)
	                       	$tmp = $tableName.".`".$order[0]."` ".$order[1];
                        else{
                          
                          $tmp = '`'.$tableName."`.".$order[0];
                          if (isset($this->aggregated_columns[$tmp]))
                            $tmp = '`'.$this->aggregated_columns[$tmp].'`';
                          
                          $tmp = " ".$tmp." ".$order[1];
                        }	
                       }  
                   
	                   if (!isset($order[2]) || $order[2] ==='')
		                   $orderby[] = $tmp; //to może psuć w przypadku kiedy będą łaczone riorytety i niepriorytety (ale to nie powinno sie nigdy zdażyc)
		               else 
			               $orderby[$order[2]] = $tmp;
                   }
                   /*if(!$order_empty)
                   	$orderby.= ", ";
                   
                   $orderby.= implode(", ", $tmp);*/ 
              }
			$limit = '';
			if (isset($crit_data['limit']) )
        	$limit =$crit_data['limit']; 
			if( !empty($limit) ){
				$limitby.= "\r\n LIMIT $limit[0], $limit[1]";
			}	
		}
		ksort($orderby);
		$orderby = implode(", \r\n\t",$orderby);
		if (!empty($orderby))
			$orderby = "\r\n ORDER BY \r\n\t".$orderby;
		
		return $groupby.$orderby.$limitby;				
	}
	
	public function encodeCustomString($str) {
		return mysql_real_escape_string($str, $this->dbConnection);
	}
	
	public static function executionTime( $onoff = true ) {
		self::$query_time_count = $onoff ;
		return self::$query_time ;
	}
}
?>